/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.editor.mouseoveraction;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.Request;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.SelectionHandlesEditPolicy;
import org.eclipse.swt.widgets.Display;


/**
 * EditPolicy installed by the manager.
 * This editPolicy takes in charge :
 * <ul>
 * <li>routing of MOUSE_OVER request</li>
 * <li></li>
 * <li></li>
 * </ul>
 * 
 * A SelectionEditPolicy which manages a List of handles provided by the subclass. Handles
 * are Figures which are added to the HANDLE layer, and generally return a DragTracker
 * for dragging them. Handles are accessible for keyboard use if they return an accessible
 * location.
 * <P>
 * SelectionHandlesEditPolicy implements {@link org.eclipse.core.runtime.IAdaptable} for
 * accessibility support. If any of the managed Handles provide accesible locations, then
 * a {@link org.eclipse.gef.AccessibleHandleProvider} is automatically created.
 * 
 * @todo Add the (CTRL, SHIFT) key modifiers. Show different handle according to the modifier. The key check
 * should be done in showTargetFeedback and eraseTargetFeedback. The appropriate handles are added/removed
 * according to the key. 3 list should exist (no key pressed, CTRL, SHIFT). To check the key, see example in
 * SelectEditPartTracker.performSelection();
 */
public class ActionHandleEditPolicy extends SelectionHandlesEditPolicy implements MouseMotionListener
{

	/**
	 * 
	 */
	static final private int STATE_ADDED   = 1;

	/**
	 * 
	 */
	static final private int STATE_REMOVED = 2;

	/**
	 * 
	 */
	private int state;    

	/**
	 * 
	 */
	protected List handles;

	/**
	 * The associated manager.
	 */
	protected ActionHandleManager manager;

	/**
	 * The amount of time to wait before hiding the diagram assistant after it
	 * has been made visible.
	 */
	private static final int DISAPPEARANCE_DELAY = 2000;

	/**
	 * The amount of time to wait before hiding the diagram assistant after the
	 * user has moved the mouse outside of the editpart.
	 */
	private static final int DISAPPEARANCE_DELAY_UPON_EXIT = 1000;

	/**
	 * The current mouse location within the host used to determine where the
	 * diagram assistant should be displayed. This will be null if the mouse is
	 * outside the host and diagram assistant figure.
	 */
	private Point mouseLocation;
	/** Flag to indicate that the diagram assistant should not be hidden. */
	private boolean avoidHidingDiagramAssistant = true;
	/** Runnable used to hide the assistant after a delay. */
	private HideDiagramAssistantRunnable hideDiagramAssistantRunnable = new HideDiagramAssistantRunnable();


	/**
	 * Constructor.
	 * 
	 * @param manager 
	 */
	public ActionHandleEditPolicy(ActionHandleManager manager) 
	{
		super();
		this.manager = manager;
		state = STATE_REMOVED;

	}

	public void activate()
	{
//		((GraphicalEditPart) getHost()).getFigure().addMouseMotionListener(this);
		super.activate();
	}

	/**
	 * Deactivate the handles.
	 */
	@Override
	public void deactivate () {
//		((GraphicalEditPart) getHost()).getFigure().removeMouseMotionListener(this);
		removeOverHandles();
		super.deactivate ();
	}

	/**
	 * Shows or updates <i>target feedback</i> for the specified <code>Request</code>. This
	 * method may be called repeatedly for the purpose of updating feedback based on changes
	 * to the Request.
	 * <P>
	 * Does nothing if the EditPolicy does not recognize the given request.
	 * <P>
	 * This method is declared on {@link EditPart#showTargetFeedback(Request) EditPart}, and
	 * is redeclared here so that EditPart can delegate its implementation to each of its
	 * EditPolicies.
	 * 
	 * @param request the Request
	 */
	@Override
	public void showTargetFeedback (Request request) {
		if (REQ_SELECTION.equals(request.getType())) {
			// we are on the shape or on the assistant.
			setAvoidHidingDiagramAssistant(true);
			if (state == STATE_REMOVED) {
				addOverHandles();
				state = STATE_ADDED;
			}
		}
	}

	/**
	 * Erases target feedback based on the given <code>Request</code>. Does nothing if the
	 * EditPolicy does not apply to the given Request.
	 * <P>
	 * This method is declared on {@link EditPart#eraseTargetFeedback(Request) EditPart}, and
	 * is redeclared here so that EditPart can delegate its implementation to each of its
	 * EditPolicies.
	 * 
	 * @param request the Request
	 */
	@Override
	public void eraseTargetFeedback (Request request) {
		if (REQ_SELECTION.equals(request.getType())) {
			// we leave  the shape or  the assistant.
			if (state == STATE_ADDED) {
//				removeOverHandles ();
				hideDiagramAssistantAfterDelay(getDisappearanceDelayUponExit());
				setAvoidHidingDiagramAssistant(false);
				state = STATE_REMOVED;
			}
		}
	}

	/**
	 * Erase assistants
	 */
	synchronized protected void removeOverHandles() {
		if (handles == null) {
			return;
		}
		IFigure layer = getLayer(LayerConstants.HANDLE_LAYER);
		for (int i = 0; i < handles.size(); i++) {
			IFigure h = (IFigure)handles.get(i);
			layer.remove(h);
			getHost().getViewer().getVisualPartMap().remove(h);
		}
		handles = null;

		// Change state
		state = STATE_REMOVED;
	}

	/**
	 * Show assistants
	 */
	protected void addOverHandles() {
		IFigure layer = getLayer(LayerConstants.HANDLE_LAYER);
		createOverHandles();
		for (int i = 0; i < handles.size(); i++) {
			IFigure h = (IFigure)handles.get(i);
			layer.add(h);
			getHost().getViewer().getVisualPartMap().put(h, getHost());
		}
	}

	/**
	 * 
	 */
	protected void createOverHandles()
	{
		if( handles != null) {
			return;
		}

		handles = manager.getHandles();
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	@Override
	protected List createSelectionHandles() {
		return new ArrayList();
	}

	/**
	 * Returns <code>true</code> if this EditPolicy understand the specified request.
	 * <P>
	 * This method is declared on {@link EditPart#understandsRequest(Request) EditPart}, and
	 * is redeclared here so that EditPart can delegate its implementation to each of its
	 * EditPolicies. <code>EditPart</code> returns <code>true</code> if any of its
	 * EditPolicies returns <code>true</code>.  In other words, it performs a logical OR.
	 * 
	 * @param request the Request
	 * 
	 * @return boolean <code>true</code> if the EditPolicy understands the specified request
	 * 
	 * @see EditPart#understandsRequest(Request)
	 */
	@Override
	public boolean understandsRequest(Request request) {
		return super.understandsRequest(request);
	}

	/**
	 * 
	 * 
	 * @param request 
	 * 
	 * @return 
	 */
	@Override
	public Command getCommand(Request request) {
		return super.getCommand(request);
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	protected IFigure getConnectionFigure(){
		return ((GraphicalEditPart)getHost()).getFigure();
	}

	/**
	 * 
	 * 
	 * @return 
	 */
	protected GraphicalEditPart getGraphicalEditPart () {
		return (GraphicalEditPart)getHost();
	}

	/**
	 * Checks if the object is or is part of the diagram assistant figure. This
	 * is used to determine if the mouse is hovering over the diagram assistant.
	 * 
	 * @param object
	 *            the object in question
	 * @return True if the object in question is or is part of the diagram
	 *         assistant figure; false otherwise.
	 */
	protected boolean isDiagramAssistant(Object object)
	{
		//TODO check effectively if the object is the assistant.
		return true;
	}

	/**
	 * Returns true if the diagram assistant is currently showing; false
	 * otherwise. This is used to determine if the diagram assistant should be
	 * shown or hidden at a given point in time.
	 * 
	 * @return true if the diagram assistant is showing; false otherwise
	 */
	protected  boolean isDiagramAssistantShowing()
	{
		return handles!=null;
	}

	/**
	 * Hides the diagram assistant after a certain amount of time has passed.
	 * 
	 * @param delay
	 *            the delay in milliseconds
	 */
	protected void hideDiagramAssistantAfterDelay(int delay) {
		if (isDiagramAssistantShowing()) {
			Display.getCurrent().timerExec(delay, hideDiagramAssistantRunnable);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.draw2d.MouseMotionListener#mouseEntered(org.eclipse.draw2d.MouseEvent)
	 */
	public void mouseEntered(MouseEvent me) {
		setMouseLocation(me.getLocation());
		// do not hide the diagram assistant if the user is hovering over it
		setAvoidHidingDiagramAssistant(isDiagramAssistant(me.getSource()));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.draw2d.MouseMotionListener#mouseExited(org.eclipse.draw2d.MouseEvent)
	 */
	public void mouseExited(MouseEvent me) {
		setMouseLocation(null);
		hideDiagramAssistantAfterDelay(getDisappearanceDelayUponExit());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.draw2d.MouseMotionListener#mouseMoved(org.eclipse.draw2d.MouseEvent)
	 */
	public void mouseMoved(MouseEvent me) {
		setMouseLocation(me.getLocation());

		// do not hide the diagram assistant if the user is hovering over it
		setAvoidHidingDiagramAssistant(isDiagramAssistant(me.getSource()));

//		showDiagramAssistantAfterDelay(getAppearanceDelay());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.draw2d.MouseMotionListener#mouseHover(org.eclipse.draw2d.MouseEvent)
	 */
	public void mouseHover(MouseEvent me) {
		// do nothing
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.draw2d.MouseMotionListener#mouseDragged(org.eclipse.draw2d.MouseEvent)
	 */
	public void mouseDragged(MouseEvent me) {
		// do nothing
	}

	/**
	 * Gets the amount of time to wait before hiding the diagram assistant after
	 * it has been made visible.
	 * 
	 * @return the time to wait in milliseconds
	 */
	protected int getDisappearanceDelay() {
		return DISAPPEARANCE_DELAY;
	}

	/**
	 * Gets the amount of time to wait before hiding the diagram assistant after
	 * the user has moved the mouse outside of the editpart.
	 * 
	 * @return the time to wait in milliseconds
	 */
	protected int getDisappearanceDelayUponExit() {
		return DISAPPEARANCE_DELAY_UPON_EXIT;
	}

	/**
	 * Gets the current mouse location. This will be null if the mouse is
	 * outside the host and diagram assistant figure.
	 * 
	 * @return Returns the current mouse location
	 */
	protected Point getMouseLocation() {
		return mouseLocation;
	}

	/**
	 * Sets the current mouse location. If set to null, this implies that the
	 * mouse is outside the host and diagram assistant figure.
	 * 
	 * @param mouseLocation
	 *            the current mouse location
	 */
	protected void setMouseLocation(Point mouseLocation) {
		this.mouseLocation = mouseLocation;
	}

	/**
	 * Sets the flag to indicate that the diagram assistant should not be
	 * hidden.
	 * 
	 * @param avoidHidingDiagramAssistant
	 *            Flag to indicate that the diagram assistant should not be
	 *            hidden
	 */
	protected void setAvoidHidingDiagramAssistant(boolean avoidHidingDiagramAssistant) {
		this.avoidHidingDiagramAssistant = avoidHidingDiagramAssistant;
	}

	/**
	 * Returns true if the diagram assistant should not be hidden; false
	 * otherwise.
	 * 
	 * @return true if the diagram assistant should not be hidden; false
	 *         otherwise.
	 */
	protected boolean shouldAvoidHidingDiagramAssistant() {
		return avoidHidingDiagramAssistant;
	}

	/**
	 * The <code>Runnable</code> used when a timer is started to hide the
	 * diagram assistant after a certain amount of time has passed.
	 */
	private class HideDiagramAssistantRunnable implements Runnable
	{

		/**
		 * The diagram assistant is removed when this task is run if the mouse
		 * is still outside the shape.
		 */
		public void run() {
			if ( !shouldAvoidHidingDiagramAssistant()) {
				removeOverHandles();
			}
		}
	}

}
